package com.lizemin.annotation;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.lizemin.util.YamlParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author lzm
 * @date 2025/4/22 19:45
 * @description 最简单的feign客户端代理实现
 * <p>
 * 目前只支持post请求，并且只支持一个参数的post请求，并且参数必须为@RequestBody注解修饰
 * 或者没有参数也行
 */
@Slf4j
public class SimpleFeignClientAspect implements InvocationHandler {

    /**
     * 被代理的接口class对象
     */
    private final Class<?> clazz;

    /**
     * 注册中心地址
     */
    private final String registerCenterAddr;

    /**
     * 构造器
     * @param clazz 接口class对象
     */
    public SimpleFeignClientAspect(Class<?> clazz) {
        registerCenterAddr = YamlParser.getNacosServerAddr();
        this.clazz = clazz;
    }

    /**
     * 计数器，用于实现负载均衡的轮询策略
     */
    private static final AtomicLong count = new AtomicLong(0);

    /**
     * 代理方法, 我们注意看这个方法，是不是和spring aop的环绕通知很像？实际上，spring aop就是基于动态代理实现的
     * @param method 被代理的方法
     * @param args    方法参数
     * @return 方法返回值
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();
        if (StrUtil.equals("toString", name)) {
            return Integer.toHexString(System.identityHashCode(proxy));
        }
        if (StrUtil.equals("hashCode", name)) {
            return proxy.hashCode();
        }
        if (StrUtil.equals("equals", name)) {
            return proxy == args[0];
        }
        FeignClient feignClient = clazz.getAnnotation(FeignClient.class);
        if (Objects.isNull(feignClient)) {
            throw new RuntimeException("请在接口上添加@FeignClient注解");
        }
        Instance instance = getInstanceFromRegisterCenter(feignClient);
        String ip = instance.getIp();
        int port = instance.getPort();
        PostMapping postMapping = method.getAnnotation(PostMapping.class);
        if (Objects.isNull(postMapping)) {
            throw new RuntimeException("请在方法上添加@PostMapping注解");
        }
        String[] value = postMapping.value();
        String url = StrUtil.format("http://{}:{}{}", ip, port, value[0]);
        log.info("请求的url为：{}", url);
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            Annotation[] annotations = parameterAnnotations[i];
            for (Annotation annotation : annotations) {
                if (annotation instanceof RequestBody) {
                    Object body = args[i];
                    log.info("请求体参数为：{}", body);
                    String result = HttpUtil.post(url, JSONUtil.toJsonStr(body));
                    Class<?> returnType = method.getReturnType();
                    // 需要注意的是：这里返回的是方法执行后的结果，而不是代理对象
                    return JSONUtil.toBean(result, returnType);
                }
            }
        }
        // 需要注意的是：这里返回的是方法执行后的结果，而不是代理对象
        String result = HttpUtil.post(url, "");
        Class<?> returnType = method.getReturnType();
        // 需要注意的是：这里返回的是方法执行后的结果，而不是代理对象
        return JSONUtil.toBean(result, returnType);
    }

    /**
     * 从注册中心获取服务实例
     *
     * @param feignClient feign客户端注解
     * @return 服务实例
     * @throws NacosException nacos异常
     */
    private Instance getInstanceFromRegisterCenter(FeignClient feignClient) throws NacosException {
        String serviceName = feignClient.name();
        // 1. 创建 NamingService 实例
        NamingService naming = NacosFactory.createNamingService(registerCenterAddr);

        // 2. 获取服务实例列表
        List<Instance> instances = naming.getAllInstances(serviceName);
        if (CollectionUtil.isEmpty(instances)) {
            throw new RuntimeException("未找到服务" + serviceName + "的实例");
        }

        // 模拟轮询策略
        int size = instances.size();
        int index = count.intValue() % size;
        count.incrementAndGet();
        return instances.get(index);
    }


}
